#include <stdlib.h>
#include <string.h>
#include "gc.h"
#include "mem.h"
#include "redmapobject.h"
#include "redcodeobject.h"
#include "redarrayobject.h"
#include "redframeobject.h"
#include "redstringobject.h"

static void frame_clear(RedObject *self)
{
	RedFrameObject *frame = (RedFrameObject *)self;

	for (int i = 0; i < frame->sp; i++)			Red_UNREF(frame->stack[i]);
	for (int i = 0; i < frame->blocks.pb; i++)	Red_UNREF(frame->blocks.blocks[i]);

	Red_UNREF(frame->code);
	Red_UNREF(frame->locals);
	Red_UNREF(frame->closure);
}

static void frame_release(RedObject *self)
{
	RedFrameObject *frame = (RedFrameObject *)self;

	frame_clear(self);
	RedMem_Free(frame->stack);
	RedMem_Free(frame->blocks.ssp);
	RedMem_Free(frame->blocks.blocks);
	Red_RELEASE();
}

static void frame_traverse(RedObject *self, tp_visit_fn_t visit, void *args)
{
	RedFrameObject *frame = (RedFrameObject *)self;

	for (int i = 0; i < frame->sp; i++)			Red_VISIT(frame->stack[i]);
	for (int i = 0; i < frame->blocks.pb; i++)	Red_VISIT(frame->blocks.blocks[i]);

	Red_VISIT(frame->locals);
	Red_VISIT(frame->closure);
}

RedTypeObject RedType_Frame =
{
	RedType_INIT("frame", RedFrameObject, RedType_Object)

	.tp_clear		= frame_clear,
	.tp_release		= frame_release,
	.tp_traverse	= frame_traverse,
};

RED_EXPORT RedObject *RedFrame_Acquire(RedObject *code, RedObject *locals, RedObject *closure)
{
	RedObject *result = RedObject_Create(&RedType_Frame, NULL);
	RedFrameObject *self = (RedFrameObject *)result;

	if (!result)
		return NULL;

	Red_REF(code);
	Red_REF(locals);
	Red_REF(closure);

	self->pc = 0;
	self->sp = 0;
	self->asp = -1;
	self->code = code;
	self->stack = RedMem_Alloc(sizeof(RedObject *) * FRAME_STACK_SIZE);
	self->locals = locals;
	self->closure = closure;
	self->capacity = FRAME_STACK_SIZE;

	self->blocks.pb = 0;
	self->blocks.ssp = RedMem_Alloc(sizeof(int) * FRAME_STACK_SIZE);
	self->blocks.blocks = RedMem_Alloc(sizeof(RedObject *) * FRAME_STACK_SIZE);
	self->blocks.capacity = FRAME_STACK_SIZE;

	if (!(self->stack) || !(self->blocks.ssp) || !(self->blocks.blocks))
	{
		Red_UNREF(result);
		return NULL;
	}

	RedObject_GC_Insert(result);
	RedObject_GC_Insert(self->locals);
	return result;
}
